#!/usr/bin/perl
#
#    Copyright (c) 1998-1999 TiVo, Inc.
#      Original ELF parsing code.
#
#    Copyright (c) 1999 Grant Erickson <grant@lcse.umn.edu>
#      Original code from 'mkevimg'.
#
#    Module name: mkirimg
#
#    Description:
#      Reads an ELF file and assigns global variables 'imageSect_start',
#      'imageSect_size', 'initrdSect_start', and 'initrdSect_size' from
#      the "image" and "initrd" section header information. It then
#      rewrites the input ELF file with assigned globals to an output
#      file.
#
#      An input file, "irSectStart.txt" has the memory address of
#      'irSectStart'. The irSectStart memory address is used to find
#      the global variables in the ".data" section of the ELF file.
#      The 'irSectStart' and the above global variables are defined
#      in "irSect.c".
#
#

use File::Basename;
use Getopt::Std;

#
# usage()
#
# Description:
#   This routine prints out the proper command line usage for this program
#
# Input(s):
#   status - Flag determining what usage information will be printed and what
#            the exit status of the program will be after the information is
#            printed.
#
# Output(s):
#   N/A
#
# Returns:
#   This subroutine does not return.
#

sub usage {
  my($status);
  $status = $_[0];

  printf("Usage: %s [-hvV] <ELF input file> <Evaluation board output file> <irSectStart.txt file>\n",
	 $program);

  if ($status != 0) {
    printf("Try `%s -h' for more information.\n", $program);
  }

  if ($status != 1) {
    print("  -h             Print out this message and exit.\n");
    print("  -v             Verbose. Print out lots of ELF information.\n");
    print("  -V             Print out version information and exit.\n");
  }

  exit($status);
}

#
# version()
#
# Description:
#   This routine prints out program version information
#
# Input(s):
#   N/A
#
# Output(s):
#   N/A
#
# Returns:
#   This subroutine does not return.
#

sub version {
  print("mkirimg Version 1.1.0\n");
  print("Copyright (c) 1998-1999 TiVo, Inc.\n");
  print("Copyright (c) 1999 Grant Erickson <grant\@lcse.umn.edu>\n");

  exit (0);
}

#
# file_check()
#
# Description:
#   This routine checks an input file to ensure that it exists, is a
#   regular file, and is readable.
#
# Input(s):
#   file - Input file to be checked.
#
# Output(s):
#   N/A
#
# Returns:
#   0 if the file exists, is a regular file, and is readable, otherwise -1.
#

sub file_check {
  my($file);
  $file = $_[0];

  if (!(-e $file)) {
     printf("The file \"%s\" does not exist.\n", $file);
     return (-1);
  } elsif (!(-f $file)) {
     printf("The file \"%s\" is not a regular file.\n", $file);
     return (-1);
  } elsif (!(-r $file)) {
     printf("The file \"%s\" is not readable.\n", $file);
     return (-1);
  }

  return (0);
}

#
# decode_options()
#
# Description:
#   This routine steps through the command-line arguments, parsing out
#   recognzied options.
#
# Input(s):
#   N/A
#
# Output(s):
#   N/A
#
# Returns:
#   N/A
#

sub decode_options {

  if (!getopts("hvV")) {
      usage(1);
  }

  if ($opt_h) {
      usage(0);
  }

  if ($opt_V) {
      version();
      exit (0);
  }

  if ($opt_v) {
      $verbose = 1;
  }

  if (!($ElfFile = shift(@ARGV))) {
      usage(1);
  }

  if (!($OutputFile = shift(@ARGV))) {
      usage (1);
  }

  if (!($IrFile = shift(@ARGV))) {
      usage (1);
  }

  if (file_check($ElfFile)) {
      exit(1);
  }

  if (file_check($IrFile)) {
      exit(1);
  }
}

#
# ELF file and section header field numbers
#

require 'elf.pl';

#
# Main program body
#

{
  $program = basename($0);
  decode_options();

  open(ELF, "<$ElfFile") || die "Cannot open input file";
  open(OUTPUT, ">$OutputFile") || die "Cannot open output file";
  open(IR, "$IrFile") || die "Cannot open input file";

  $ElfFilesize = (-s $ElfFile);

  if (read(ELF, $ibuf, $ElfFilesize) != $ElfFilesize) {
    print("Failed to read ELF input file!\n");
    exit(1);
  }

  if (read(IR, $irbuf, 8) != 8) {
      print("Failed to read Ir input file!\n");
      exit(1);
  }

  #
  # Parse ELF header
  #

  @eh = unpack("a16n2N5n6", $ibuf);

  #
  # Make sure this is actually a PowerPC ELF file.
  #

  if (substr($eh[$e_ident], 0, 4) ne "\177ELF") {
      printf("The file \"%s\" is not an ELF file.\n", $ElfFile);
      exit (1);
  } elsif ($eh[$e_machine] != 20) {
      printf("The file \"%s\" is not a PowerPC ELF file.\n", $ElfFile);
      exit (1);
  }

  #
  # Find the section header for the string table.
  #

  $strtable_section_offset =  $eh[$e_shoff] +

  $eh[$e_shstrndx] * $eh[$e_shentsize];

  if ($verbose) {
     printf("String table section header offset: 0x%x\n",
     $strtable_section_offset);
  }

  #
  # Find the start of the string table.
  #

  @strh = unpack("N10", substr($ibuf, $strtable_section_offset,
			       $eh[$e_shentsize]));

  if ($verbose) {
     printf("Section name strings start at: 0x%x, %d bytes.\n",
     $strh[$sh_offset], $strh[$sh_size]);
  }

  $names = substr($ibuf, $strh[$sh_offset], $strh[$sh_size]);

  # Grab each section header and find '.data', 'image', and 
  # 'initrd' sections in particular.

  $off = $eh[$e_shoff];
  $imageFound = 0;
  $initrdFound = 0;
  
  for($i = 0; $i < $eh[$e_shnum]; $i++, $off += $eh[$e_shentsize]) {
    @sh = unpack("N10", substr($ibuf, $off, $eh[$e_shentsize]));

    # Take the first section name from the array returned by split.

    ($name) = split(/\000/, substr($names, $sh[$sh_name]));

    # Attempt to find the .data, image, and initrd sections

    if ($name =~ /^\image$/) {
      ($image_addr, $image_offset, $image_size) =
	($sh[$sh_addr], $sh[$sh_offset], $sh[$sh_size]);
      $imageFound = 1;

    } elsif ($name =~ /^\initrd$/) {
      ($initrd_addr, $initrd_offset, $initrd_size) =
	($sh[$sh_addr], $sh[$sh_offset], $sh[$sh_size]);
      $initrdFound = 1;

    } elsif ($name =~ /^\.data$/) {
      ($data_addr, $data_offset, $data_size) =
	($sh[$sh_addr], $sh[$sh_offset], $sh[$sh_size]);

    } elsif ($name =~ /^\.bss$/) {
      ($bss_addr, $bss_offset, $bss_size) =
	($sh[$sh_addr], $sh[$sh_offset], $sh[$sh_size]);

    }
   }

  if ($verbose) {
    printf("Data section   - Address: 0x%08x, Size: 0x%08x, File Offset 0x%08x\n",
	   $data_addr, $data_size, $data_offset);
    printf("Bss  section   - Address: 0x%08x, Size: 0x%08x, File Offset 0x%08x\n",
	 $bss_addr, $bss_size, $bss_offset);
   }

  if ($verbose) {
    if ($imageFound) {
      printf("Image section  - Address: 0x%08x, Size: 0x%08x\n",
	     $image_addr, $image_size);
    } else {
      printf("Image section not found in file: $ElfFile\n");
    }

    if ($initrdFound) {
      printf("Initrd section - Address: 0x%08x, Size: 0x%08x\n",
	     $initrd_addr, $initrd_size);
    } else {
      printf("Initrd section not found in file: $ElfFile\n");
    }
  }

  # get file offset of irSectStart

  $irSectStartoffset = hex ($irbuf);

  if ($verbose) {
    printf("irSectStartOffset Address: 0x%08x\n", $irSectStartoffset);
  }

  # get the offset of global variables 

  $initialOffset = ($irSectStartoffset - $data_addr) + $data_offset + 4;

  # write modified values to OUTPUT file

  syswrite(OUTPUT, $ibuf, $initialOffset);

  if ($imageFound) {
    $testN = pack ("N2", $bss_addr + $bss_size, $image_size);
    syswrite(OUTPUT, $testN, length($testN));
    printf("Updated symbol \"imageSect_start\" to 0x%08x\n",
	   $bss_addr + $bss_size);
    printf("Updated symbol \"imageSect_size\" to 0x%08x\n", $image_size);
  } else {
    syswrite(OUTPUT, $ibuf, 8, $initialOffset);
  }

  if ($initrdFound) {
    $testN = pack ("N2", $bss_addr + $bss_size + $image_size, $initrd_size);
    syswrite(OUTPUT, $testN, length($testN));
    printf("Updated symbol \"initrdSect_start\" to 0x%08x\n",
	   $bss_addr + $bss_size + $image_size);
    printf("Updated symbol \"initrdSect_size\" to 0x%08x\n", $initrd_size);
  } else {
    syswrite(OUTPUT, $ibuf,8, $initialOffset + 8);
  }

  syswrite(OUTPUT, $ibuf, $ElfFilesize - ($initialOffset + 16),
	   $initialOffset + 16);

  #
  # Clean-up and leave
  #

  close (ELF);
  close (OUTPUT);
  close (IR);

  exit (0);
}

